/*
  +----------------------------------------------------------------------+
  | PHP Version 7                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2015 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author:                                                              |
  +----------------------------------------------------------------------+
*/

/* $Id$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_road.h"

/* If you declare any globals in php_road.h uncomment this:
ZEND_DECLARE_MODULE_GLOBALS(road)
*/

/* True global resources - no need for thread safety here */
static int le_road;

zend_class_entry *road_ce;

ZEND_BEGIN_ARG_INFO_EX(arginfo_road_set, 0, 0, 1)
    ZEND_ARG_INFO(0, obj)
    ZEND_ARG_INFO(0, func)
    ZEND_ARG_INFO(0, param)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_road_deal, 0, 0, 1)
    ZEND_ARG_INFO(0, deal)
ZEND_END_ARG_INFO()

/* {{{ road_functions[]
 *
 * Every user visible function must have an entry in road_functions[].
 */
const zend_function_entry road_methods[] = {
	PHP_ME(road, set,	arginfo_road_set, ZEND_ACC_PUBLIC)		/* For testing, remove later. */
	PHP_ME(road, deal,	arginfo_road_deal, ZEND_ACC_PUBLIC)		/* For testing, remove later. */
	PHP_ME(road, get,	NULL, ZEND_ACC_PUBLIC)		/* For testing, remove later. */
    {NULL, NULL, NULL}	/* Must be the last line in road_functions[] */
};
/* }}} */

/* {{{ PHP_INI
 */
/* Remove comments and fill if you need to have entries in php.ini
PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("road.global_value",      "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_road_globals, road_globals)
    STD_PHP_INI_ENTRY("road.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_road_globals, road_globals)
PHP_INI_END()
*/
/* }}} */

/* Remove the following function when you have successfully modified config.m4
   so that your module can be compiled into PHP, it exists only for testing
   purposes. */

zend_bool road_call(zval *retval, zval *obj, zval *function_name, zval *params, size_t params_count)
{
    zend_fcall_info fc_info = empty_fcall_info;
    zend_fcall_info_cache fc_cache = empty_fcall_info_cache;
        
    fc_info.param_count = params_count;
    fc_info.params = params;
    fc_info.size = sizeof(fc_info);
    fc_info.function_table = EG(function_table);
    fc_info.function_name = *function_name;
    if (obj != NULL && Z_TYPE_P(obj) == IS_OBJECT) {
        fc_info.object = Z_OBJ_P(obj); 
    }
    fc_info.retval = retval;
    fc_info.no_separation = 0; 
    if (zend_call_function(&fc_info, &fc_cache) == SUCCESS) {
        return 1;
    } else {
        return 0;
    } 
}

/* Every user-visible function in PHP should document itself in the source */
/* {{{ proto object Road::set(mixed callback [, mixed userdata])
   Set callback function for getting data */
PHP_METHOD(road, set)
{
    zval  *func, *userdata = NULL;

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|z", &func, &userdata) == FAILURE) {
		return;
	}
    	
	zend_update_property(road_ce, getThis(), "func_get", sizeof("func_get")-1, func);
	if (userdata) {
        zend_update_property(road_ce, getThis(), "userdata", sizeof("userdata")-1, userdata);
    }
    RETURN_TRUE;
}
/* }}} */

/* {{{ proto object Road::deal(mixed callback)
   Set function for processing data */
PHP_METHOD(road, deal)
{
    zval *func, *deal, *rv, tmp;
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &func) == FAILURE) {
        return;
    }

    deal = zend_read_property(road_ce, getThis(), "func_deal", sizeof("func_deal")-1, 1, rv);
    if (Z_TYPE_P(deal) != IS_ARRAY) {
        array_init(deal);
    }
    ZVAL_COPY(&tmp, func);
    add_next_index_zval(deal, &tmp);
	zend_update_property(road_ce, getThis(), "func_deal", sizeof("func_deal")-1, deal);

    RETURN_TRUE;
}
/* }}} */

/* {{{ proto object Road::get()
Get data*/
PHP_METHOD(road, get)
{
    zval *obj, *func, *param, *rv, *func_get, *func_deal, retval, tmp, *item;
    HashPosition pointer;
    HashTable *hash;
    zend_string *func_name;
    zend_fcall_info_cache fcc;
    char *error = NULL;

    func_get = zend_read_property(road_ce, getThis(), "func_get", sizeof("func_get")-1, 1, rv);
    param = zend_read_property(road_ce, getThis(), "userdata", sizeof("userdata")-1, 1, rv);
    
    if (!zend_is_callable_ex(func_get, NULL, IS_CALLABLE_STRICT, &func_name, &fcc, &error)) {
        php_error(E_WARNING, "The function which setted by Road::set() can not be called. %s", error);
        zend_string_release(func_name);
        if (error) {
            efree(error);
        }
        return; 
    }
    
    if (Z_TYPE_P(func_get) == IS_ARRAY) {
        obj = zend_hash_index_find(Z_ARRVAL_P(func_get), 0);
        func = zend_hash_index_find(Z_ARRVAL_P(func_get), 1);
    } else if (Z_TYPE_P(func_get) == IS_STRING) {
        obj = NULL;
        func = func_get;
    }
    if (road_call(&retval, obj, func, param, 1) == 0) {
        if (obj) {
            php_error(E_WARNING, "fail to call method '%s'", Z_STRVAL_P(func));
        } else {
            php_error(E_WARNING, "fail to call function '%s'", Z_STRVAL_P(func));
        }
    }

    if (Z_TYPE(retval) == IS_UNDEF) {
        return;
    }
    
    func_deal = zend_read_property(road_ce, getThis(), "func_deal", sizeof("func_deal") - 1, 1, rv);
    if (Z_TYPE_P(func_deal) != IS_ARRAY) {
        RETURN_ZVAL(&retval, 0, 1);
    }
    hash = Z_ARRVAL_P(func_deal);
    zend_hash_internal_pointer_reset_ex(hash, &pointer);
    for (; NULL != (item = zend_hash_get_current_data_ex(hash, &pointer)); zend_hash_move_forward_ex(hash, &pointer)) {
        if (!zend_is_callable_ex(item, NULL, IS_CALLABLE_STRICT, &func_name, &fcc, &error)) {
            php_error(E_WARNING, "The function which setted by Road::deal() can not be called.%s", error);
            zend_string_release(func_name);
            if (error) {
                efree(error);
            }
            continue; 
        }
        if (Z_TYPE_P(item) == IS_ARRAY) {
            obj = zend_hash_index_find(Z_ARRVAL_P(item), 0);
            func = zend_hash_index_find(Z_ARRVAL_P(item), 1);
        } else if (Z_TYPE_P(item) == IS_STRING) {
            obj = NULL;
            func = item;
        }
        ZVAL_COPY(&tmp, &retval);
        if (road_call(&retval, obj, func, &tmp, 1) == 0) {
            if (obj) {
                php_error(E_WARNING, "fail to call method '%s'.", Z_STRVAL_P(func));
            } else {
                php_error(E_WARNING, "fail to call function '%s'.", Z_STRVAL_P(func));
            }
        }
    }
    
    if (Z_TYPE(retval) != IS_UNDEF) {
        RETURN_ZVAL(&retval, 0, 1);
    } else {
        return;
    } 
    
}

/* {{{ php_road_init_globals
 */
/* Uncomment this function if you have INI entries
static void php_road_init_globals(zend_road_globals *road_globals)
{
	road_globals->global_value = 0;
	road_globals->global_string = NULL;
}
*/
/* }}} */

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(road)
{
    zend_class_entry ce;

    INIT_CLASS_ENTRY(ce, "road", road_methods);
    road_ce = zend_register_internal_class(&ce);
    zend_declare_property_null(road_ce, "func_get", sizeof("func_get") - 1, ZEND_ACC_PRIVATE);
    zend_declare_property_null(road_ce, "userdata", sizeof("userdata") - 1, ZEND_ACC_PRIVATE);
    zend_declare_property_null(road_ce, "func_deal", sizeof("func_deal") - 1, ZEND_ACC_PUBLIC);

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(road)
{
	/* uncomment this line if you have INI entries
	UNREGISTER_INI_ENTRIES();
	*/
	return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request start */
/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(road)
{
#if defined(COMPILE_DL_ROAD) && defined(ZTS)
	ZEND_TSRMLS_CACHE_UPDATE();
#endif
	return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request end */
/* {{{ PHP_RSHUTDOWN_FUNCTION
 */
PHP_RSHUTDOWN_FUNCTION(road)
{
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(road)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "road support", "enabled");
	php_info_print_table_end();

	/* Remove comments if you have entries in php.ini
	DISPLAY_INI_ENTRIES();
	*/
}
/* }}} */

/* {{{ road_module_entry
 */
zend_module_entry road_module_entry = {
	STANDARD_MODULE_HEADER,
	"road",
	road_methods,
	PHP_MINIT(road),
	PHP_MSHUTDOWN(road),
	PHP_RINIT(road),		/* Replace with NULL if there's nothing to do at request start */
	PHP_RSHUTDOWN(road),	/* Replace with NULL if there's nothing to do at request end */
	PHP_MINFO(road),
	PHP_ROAD_VERSION,
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_ROAD
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE();
#endif
ZEND_GET_MODULE(road)
#endif

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
